let NLF = new Array(2).fill().map(() =>
	new Array(2).fill().map(() =>
		new Array(2).fill().map(() =>
			new Array(2).fill().map(() =>
				new Array(2).fill(0)
			)
		)
	)
);

NLF[0][0][0][0][0] = 0;
NLF[0][0][0][0][1] = 1;
NLF[0][0][0][1][0] = 1;
NLF[0][0][0][1][1] = 1;
NLF[0][0][1][0][0] = 0;
NLF[0][0][1][0][1] = 1;
NLF[0][0][1][1][0] = 0;
NLF[0][0][1][1][1] = 0;

NLF[0][1][0][0][0] = 0;
NLF[0][1][0][0][1] = 0;
NLF[0][1][0][1][0] = 1;
NLF[0][1][0][1][1] = 0;
NLF[0][1][1][0][0] = 1;
NLF[0][1][1][0][1] = 1;
NLF[0][1][1][1][0] = 1;
NLF[0][1][1][1][1] = 0;

NLF[1][0][0][0][0] = 0;
NLF[1][0][0][0][1] = 0;
NLF[1][0][0][1][0] = 1;
NLF[1][0][0][1][1] = 1;
NLF[1][0][1][0][0] = 1;
NLF[1][0][1][0][1] = 0;
NLF[1][0][1][1][0] = 1;
NLF[1][0][1][1][1] = 0;

NLF[1][1][0][0][0] = 0;
NLF[1][1][0][0][1] = 1;
NLF[1][1][0][1][0] = 0;
NLF[1][1][0][1][1] = 1;
NLF[1][1][1][0][0] = 1;
NLF[1][1][1][0][1] = 1;
NLF[1][1][1][1][0] = 0;
NLF[1][1][1][1][1] = 0;

/**
 * @param productId 设备id
 * @param counter 最大计数器，数据库的
 * @param add_credit  解锁天数
 * @param secretKey 产品秘钥
 * @param unit 密码类型，共有四个值0，1，2，3 ；0表示共享，1表示分期付款。2，3 是预留的，我们是租赁，所以用0.
 * @param sha productId.substring(productId.length()-3,productId.length()-2);
 */
function keeloqOutdata(productId, secretKey, counter, add_credit, unit =
	"0") {
	// let counter = "7";
	// let add_credit = "1";
	// let productId = "1001010";
	// let secretKey = "4F702CEF95908474";
	// let unit = "1";
	let sha = productId.substring(productId.length - 3, productId.length - 2);
	let unencryptedData1 = unencryptedData(counter, add_credit, unit, sha);
	let mypwords = secretKey;
	let hold_s = CRYPT(mypwords, unencryptedData1);
	let holdLength = hold_s.length;
	let tempL = 8 - holdLength;
	let $bsds = "";
	for (let i = 0; i < tempL; i++) {
		$bsds += "0";
	}
	hold_s = $bsds + hold_s;
	let last_device_number_str = productId.substring(productId.length - 2);
	let last_device_number = intToBinary(parseInt(last_device_number_str));
	let lastL = last_device_number.length;
	let forss = 8 - lastL;
	let bs = "";
	for (let i = 0; i < forss; i++) {
		bs += "0";
	}
	last_device_number = bs + last_device_number;

	let last_keeloq_outdata_str = hold_s.substring(hold_s.length - 2);
	let last_keeloq_outdata_a = parseInt(last_keeloq_outdata_str, 16);
	let last_keeloq_outdata = intToBinary(last_keeloq_outdata_a);
	let fors = 8 - last_keeloq_outdata.length;
	let b = "";
	for (let i = 0; i < fors; i++) {
		b += "0";
	}
	// Keeloq password processed
	last_keeloq_outdata = b + last_keeloq_outdata;
	console.log("last_keeloq_outdata：" + last_keeloq_outdata);

	// XOR operation
	let yh_data = "";

	for (let i = 0; i < last_keeloq_outdata.length; i++) {
		let num = last_device_number.charCodeAt(i);
		let out = last_keeloq_outdata.charCodeAt(i);
		yh_data += num ^ out;
	}
	yh_data = yh_data.substring(1);
	yh_data = "0" + yh_data;
	let number_outdata = intToHex(parseInt(yh_data, 2));
	if (number_outdata.length === 1) {
		number_outdata = "0" + number_outdata;
	}
	console.log("number_outdata：" + number_outdata);
	// Merge into 10 HEX
	let shi_pw = number_outdata + hold_s;
	let passcode_a = shi_pw.substring(0, 6);
	let passcode_b = shi_pw.substring(shi_pw.length - 4);
	let passcode_aa = parseInt(passcode_a, 16);
	let passcode_bb = parseInt(passcode_b, 16);
	let passcode_b_for = 5 - String(passcode_bb).length;
	let passcode_b_for_strb = "";
	let passcode_b_for_stra = "0";
	for (let i = 0; i < passcode_b_for; i++) {
		passcode_b_for_strb += passcode_b_for_stra;
	}
	passcode_b = passcode_b_for_strb + passcode_bb;
	let password_code = passcode_aa + passcode_b;
	console.log("password_code：" + password_code);
	let password_codes_for = 12 - password_code.length;
	let pwssa = "0";
	let pwssb = "";
	for (let i = 0; i < password_codes_for; i++) {
		pwssb += pwssa;
	}
	password_code = pwssb + password_code;

	return password_code;
}

function unencryptedData(content, add_credit, unit, sha) {
	let c_e = intToBinary(parseInt(content));
	let p_e = intToBinary(parseInt(add_credit));
	let d_e = intToBinary(parseInt(unit));

	let code_sub = sha;
	let code_e = intToBinary(parseInt(code_sub));
	if (p_e.length !== 10) {
		let for_ = 10 - p_e.length;
		let f = "0";
		for (let i = 0; i < for_; i++) {
			p_e = f + p_e;
		}
	}
	if (d_e.length < 2) {
		d_e = "0" + d_e;
	}

	if (code_e.length < 4) {
		let for_ = 4 - code_e.length;
		let f = "0";
		for (let i = 0; i < for_; i++) {
			code_e = f + code_e;
		}
	}

	let data = c_e + p_e + d_e + code_e;
	data = intToHex(parseInt(data, 2));
	data = "000" + data;
	return data;
}

// Helper functions for conversion
function intToBinary(num) {
	return num.toString(2);
}

function intToHex(num) {
	return num.toString(16);
}


function getBit(source, n) {
	const temp = 1n << BigInt(n);
	const temp1 = BigInt(source) & temp;
	if (temp1 != 0) {
		return 1;
	}
	return 0;
}

function RRC(soucre, c) {
	if (c !== 0) {
		soucre = (soucre >> 1) | 0x80000000;
	} else {
		soucre = (soucre >> 1) & 0x7fffffff;
	}
	return soucre;
}

function RLC(source, c) {
	return c !== 0 ? (source << 1) | 1 : (source << 1) & 0xFFFFFFFE;
}


function parseMd5L16ToLong(md5L16) {
	if (md5L16 === null) {
		throw new Error("null");
	}
	md5L16 = md5L16.toLowerCase();
	// console.log(md5L16);
	const bA = Array.from(md5L16).map(c => c.charCodeAt(0));
	let re = 0n;
	for (let i = 0; i < bA.length; i++) {
		//加下一位的字符时，先将前面字符计算的结果左移4位
		re <<= 4n;
		//0-9数组
		let b = BigInt(bA[i] - 48);
		//A-F字母
		if (b > 9n) {
			b = BigInt(b - 39n);
		}
		// console.log(b);
		//非16进制的字符
		if (b > 15n || b < 0n) {
			throw new Error("For input string '" + md5L16);
		}
		// console.log("re："+re+" ,b："+b);
		re += b;
	}
	return re;
}


function parseString16ToLong(str16) {
	if (str16 === null) {
		throw new Error("null");
	}
	//先转化为小写
	str16 = str16.toLowerCase();
	//如果字符串以0x开头，去掉0x
	str16 = str16.startsWith("0x") ? str16.substring(2) : str16;
	if (str16.length > 16) {
		throw new Error("For input string '" + str16 + "' is too long");
	}
	return parseMd5L16ToLong(str16);
}

function CRYPT(keyStr, data) {
	const key = parseString16ToLong(keyStr);
	const source = parseString16ToLong(data);
	const cryptData = CRYPTImpl((parseInt(source)), BigInt(key));
	return ('00000000' + cryptData.toString(16)).slice(-8).toUpperCase();
}

function CRYPTImpl(source, key) {
	let c;
	for (let i = 0; i < 528; i++) {
		const nlf = NLF[getBit(source, 31)][getBit(source, 26)][getBit(source, 20)][getBit(source, 9)][getBit(source, 1)];
		const y16 = getBit(source, 16);
		const y0 = getBit(source, 0);
		const k = getBit(key, i % 64);
		const result = nlf ^ y16 ^ y0 ^ k;
		c = result !== 0 ? 1 : 0;
		source = RRC(source, c);
	}
	return source;
}

function DECRYPT(source, key) {
	let c;
	for (let i = 528; i > 0; i--) {
		const nlf = NLF[getBit(source, 30)][getBit(source, 25)][getBit(source, 19)][getBit(source, 8)][getBit(source, 0)];
		const y15 = getBit(source, 15);
		const y31 = getBit(source, 31);
		const k = getBit(key, (i - 1) % 64);
		const result = nlf ^ y15 ^ y31 ^ k;
		c = result !== 0 ? 1 : 0;
		source = RLC(source, c);
	}
	return source;
}

export const lockPass = (productId, secretKey, counter, add_credit) => {
	return keeloqOutdata(productId, secretKey, counter, add_credit)
}